1.调参

作者 班级 学号
唐灏楠 AI2202 0122210880403

题目

调参,观察不同参数设置的变化

可调参数分析

在tensorflow playground里面,我发现我们主要可以调整以下参数:

  • DATA: 数据集,要分类的数据集
  • FRATURES: 输入到模型中的数据特征
  • HIDDEN LAYERS: 隐藏层,帮助模型学习更复杂的模式,但也可能导致过拟合
  • Activation:激活函数,决定了一个神经元的输出,帮助引入非线性因素,使得网络可以学习更复杂的结构
  • Learning rate:决定网络在每次迭代中更新权重的步长大小的参数
  • Regulartion:正则化,防止过拟合,太高的学习率可能导致学习过程不稳定,而太低的学习率可能使训练过程缓慢。
  • Regularization rate:正则化率,决定了正则化的强度。较高的正则化率可以减少过拟合的风险,但可能会使模型无法完全学习数据的特征。
  • Ratio of traning test:训练测试集比例,决定了数据是如何分配到训练集和测试集的。
  • Noise:模拟数据的不完美性,有助检查对噪声和异常值的鲁棒性。
  • Batch size:在训练过程中每次参数更新考虑的数据样本数量。较小的批量可以提供更频繁的更新,但可能会导致训练过程的波动增大。

调参过程指导原则

在调整神经网络的参数时,一个核心原则是尽可能简化模型,即使用最少的特征和层数来实现高效的分类或者回归。这种方法有助于减少模型的复杂度和计算成本,同时也可以避免过拟合,提高模型的泛化能力,因此,在后续的调参过程中,我将遵循这一原则,力求通过最简单的模型配置达到良好的效果(Test lost < 0.01)。这不仅涉及选择合适数量的隐藏层和节点,还包括精心挑选对模型输出影响最大的特征,以及调整学习率和正则化参数以优化性能。

对不同数据集的神经网络建模以及调参

DATA 1: Gaussian 分类问题

Pasted image 20240519150654.png

特征分析:

  1. 两组数据点,每组数据点都围绕一个中心聚集,形状类似于高斯分布的钟形曲线。
  2. 线性可分,可用X轴和Y轴特征将其直接分开

基于最简思想的参数选择

  • 激活函数:直接linear,不使用非线性激活函数。
  • 特征选择:选择X轴Y轴坐标作为特征。
  • 层数选择:直接不要隐藏层
  • 学习率:0.03
  • 噪声:不添加噪声
  • Batch size:10
  • 训练-测试比率:50%

训练效果_1

Pasted image 20240519151216.png

只用了两个特征,甚至没有使用非线性激活函数以及添加隐藏层,直接就实现了训练集上的完全分类,测试集上的误差也只有0.002,我觉得这个效果已经很好了,因此这个模型训练到这里就行了

DATA 2: Exclusive or

Pasted image 20240519151724.png

特征分析:

  1. 左上右下是一类,右上左下是另一类
  2. XY轴的正负值相乘的正负值可以用来分类这个数据集

基于最简思想的参数选择

  • 激活函数:直接linear,不使用非线性激活函数。
  • 特征选择:使用X1X2相乘的值作为特征。
  • 层数选择:直接不要隐藏层
  • 学习率:0.03
  • 噪声:不添加噪声
  • Batch size:10
  • 训练-测试比率:50%

训练效果

Pasted image 20240519152052.png

模型完美的抓住了数据集的典型特征,实现了训练集上的完美分类,测试集上也只有0.01的误差,loss随着epoch批次收敛,是一个非常好的模型。

DATA 3:Circle

Pasted image 20240519152405.png

特征分析

  • 模型很明显可以由一个园来分割,也就是可以根据x1^2 + x2^2的大小来进行分类。

基于最简思想的参数选择

  • 激活函数:直接linear,不使用非线性激活函数。
  • 特征选择:使用X1^2和X2^2的值作为特征。
  • 层数选择:直接不要隐藏层
  • 学习率:0.03
  • 噪声:不添加噪声
  • Batch size:10
  • 训练-测试比率:50%

训练效果

Pasted image 20240519152733.png

模型完美的不行,在训练集和测试集上都没有一点误差出现,甚至没有一层隐藏层。、

DATA 4:Spiral

Pasted image 20240519152930.png

特征分析

  • 这个模型看起来就很复杂了,至少对机器来说,在二维上并不能找到一个明显的线性特征边界来把这两个类分开,而且他们还形成了某种程度上的对称,这让他们更难选择用几个简单的特征来把他们分开了。

基于最简思想的参数选择——第一次

  • 激活函数: 选择sigmoid函数,以引入非线性特征。
  • 特征选择:由于一眼看不出有什么明显的特征,所以我决定直接一次选中所有能选择特征,这包括。
    • X1,X2
    • X1^2, X2^2
    • X1X2
    • sin(X1), sin(X2)
  • 层数选择:第一次先不要隐藏层看看效果
  • 学习率:0.03
  • 噪声:不添加噪声
  • Batch size:10
  • 训练-测试比率:50%

训练效果

Pasted image 20240519153521.png

验证集和训练集损失都大于0.55, 这还不如我们抛硬币来猜,看来我们必须要加一点隐藏层来捕捉一些有现有特征组成的更高层的特征来辅助我们的判断了。

基于最简思想的参数选择——第二次

  • 激活函数: 选择sigmoid函数,以引入非线性特征。
  • 特征选择:由于一眼看不出有什么明显的特征,所以我决定直接一次选中所有能选择特征,这包括。
    • X1,X2
    • X1^2, X2^2
    • X1X2
    • sin(X1), sin(X2)
  • 层数选择:
    • 一层隐藏层
    • 隐藏层8个神经元
  • 学习率:0.03
  • 噪声:不添加噪声
  • Batch size:10
  • 训练-测试比率:50%

训练效果

Pasted image 20240519153846.png

经过训练后,验证集上的损失达到了0.002,但是验证集上的损失有0.1,说明模型泛化能力不强,还需要改进。

基于最简思想的参数选择——第二次

  • 在第一次的基础上再加一层八个神经元的隐藏层

训练效果:

Pasted image 20240519154300.png

这一次,训练集的损失来到了0.001,而测试集的损失下降到了0.06,虽然理我们的标准(0.01)还有一点距离,但是我们已经可以从第二层的神经元索要识别的特征中找到了类似螺旋的结构了,这说明层数的增加有助于模型学习更复杂的特征结构。

基于最简思想的参数选择——第三次

  • 在第二次的基础上再加一层八个神经元的隐藏层

训练效果

Pasted image 20240519155250.png

这次的测试集损失更低了,但是还差一点到0.01,同时注意到训练的时间显著变长。

基于最简思想的参数选择——第四次

  • 在第三次的基础上再加一层八个神经元的隐藏层

训练效果

Pasted image 20240519155632.png

这一次虽然第四层神经元更好的识别到了螺旋的特征,而且测试集更低了,但是权重的剧烈更新简直慢的不行,在大更新前要经历相当长的啥都不变的时期,下次我要增加学习率,让它学的快点,增加步长让他别在一个局部最优解里面度过余生。

基于最简思想的参数选择——第五次

  • 在第四次的基础上,将学习率调到0.1

训练效果

Pasted image 20240519161213.png

  • 这玩意的loss不降反增了,下次还是用小一点的步长,而且有很长一段时间跟梯度消失了一样,loss一动不动,下次多加一层神经元,在用另一种激活函数试试

基于最简思想的参数选择——第六次

  • 在第五次的基础上,将学习率调到0.03,激活函数改成RELU,神经元改到6层

训练效果

Pasted image 20240519161823.png

这次真的尽力了,Testloss真的不能再低了,使用了RELU函数后训练时间开销显著减少,到了第六层的时候已经学习到了非常明显的螺旋结构,模型也还算可以了。

总结

在本次调参经验中,我深刻感受到了在机器学习项目中特征选择的重要性。良好的特征选择可以极大简化模型的复杂度,使得即使模型结构简单也能取得优异的表现。相反,如果特征选择不当,即便增加更多的层数,模型的表现也可能不尽如人意。此外,我还了解到学习率和激活函数对模型训练速度的影响显著,而且模型层数的增加并不总是带来性能的提升,尤其是考虑到时间成本后,过多的层数可能适得其反。通过这次实践,我对模型调优有了更深入的认识和体会。

2. 随机生成三类数据,每类数据包含20个样本,样本为向量表示,构建BP神经网络进行分类(从0到1的实现)

2.1 数据集的准备

由于要随机生成三类每类包含20个样本点的数据,我将随机选取三个三维的坐标,然后以它们作为中心,分别生成三个高斯分布的样本群,每个样本群有20个样本。

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
np.random.seed(50)

# 随机生成三类数据,每类20个样本,数据维度为3, 并设置分别的中心点
class1 = np.random.randn(20, 3) + [1, 1, 1]
class2 = np.random.randn(20, 3) + [4, 2, 1]
class3 = np.random.randn(20, 3) + [5, 2, 9]

X = np.vstack((class1, class2, class3)) # 按行堆叠数据
y = np.array([0]*20 + [1]*20 + [2]*20)    # 指定每个样本的类别标签

# 划分训练验证集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=50)

# 独热编码 
encoder = OneHotEncoder(sparse=False)
y_train_onehot = encoder.fit_transform(y_train.reshape(-1, 1)) 
y_test_onehot = encoder.transform(y_test.reshape(-1, 1))

2.2 BP神经网络-MLP多层感知机构建

现在我们开始实现最简单的BP神经网络:MLP多层感知机。由于MLP的前馈结构,我们可以将其拆分成许多层,每一层都应该具备3个基本的功能,它们分别是:

  1. 根据(上一层的)输入计算输出
  2. 计算参数的梯度
  3. 更新层参数

激活函数可以单独抽象为单独的一层,不具有参数。

2.2.1 基类层设计

为了形式统一,我们先定义基类,再让不同的层都继承于基类。

# 基类层
class Layer:
	# 前向传播函数,根据上一层输入x计算
	def forward(self, x):
		raise NotImplementedError # 未实现错误

	# 反向传播函数,输入下一层回传的梯度grad, 输出当前层的梯度
	def backward(self, grad):
		raise NotImplementedError

	# 更新函数,用于更新当前层的参数
	def update(self, learning_rate):
		pass

2.2.2 线性层设计

线性层是MLP中最基本的结构之一,其参数为 ( W ) 和 ( b ),输入和输出关系为: 由于其结构相当于将前后的神经元两两连接,所以也叫全连接层。

为什么 x 的维度有一个 batch_size

在神经网络训练中,通常会将多个输入样本打包成一个批次(batch)进行处理。这种方法被称为批量处理。这样做有以下几个好处:

  1. 提高计算效率:使用向量化操作处理多个样本比逐个处理单个样本要快得多。
  2. 稳定梯度下降:在每次迭代中使用一个批次的样本计算梯度,可以平滑梯度的波动,从而使训练过程更稳定。
class Linear(Layer):
	def __init__(self, num_in, num_out, use_bias=True):
		self.num_in = num_in # 输入维度
		self.num_out = num_out # 输出维度
		self.use_bias = use_bias # 是否添加偏置
	
		# 参数的初始化(绝对不能初始化为0!不然后续计算失去意义)
		# 用正态分布来初始化W
		self.W = np.random.normal(loc=0, scale=1.0, size=(num_out, num_in))
		if use_bias:
			self.b = np.zeros((1, num_out))

	def forward(self, x):
		# 前向传播 y = Wx + b
		# x的维度为(batch_size, num_in)
		self.x = x
		self.y = x @ self.W.T # y的维度为(batch_size, num_out)
		if self.use_bias:
			self.y += self.b
		return self.y

	def backward(self, grad):
		# 反向传播,按照链式法则计算
		# grad的维度为(batch_size, num_out)
		# 梯度应该对batch_size去平均值
		# grad_W的维度应该与W相同,为(num_in, num_out)
		self.grad_W = self.x.T @ grad / grad.shape[0]
		if self.use_bias:
			# grad_b的维度与b相同,(1, num_out)
			self.grad_b = np.mean(grad, axis=0, keepdims=True) # 对 grad 沿批次维度(行)取平均值,并保留维度信息以确保结果形状与偏置向量 b 一致。
		# 往上一层传递的grad维度应该为(batch_size, num_in)
		grad = grad @ self.W
		return grad

	def update(self, learning_rate):
		# 更新参数以完成梯度下降
		self.W -= learning_rate * self.grad_W
		if self.use_bias:
			self.b -= learning_rate * self.grad_b

2.2.3 激活层设计

现在我们来实现激活层的设计,以计算反向传播,这里主要实现三种激活层:

  1. Sigmoid激活层
  2. Tanh激活层
  3. ReLU激活层
  4. 啥都不动层
  5. Softmax激活层:用于多分类问题的最后一层激活,归一化所有概率
class Identity(Layer):
	# 啥都不动层
	def forward(self, x):
		return x
	def backward(self, grad):
		return grad

class Sigmoid(Layer):
	# Sigmoid激活层
	def forward(self, x):
		self.x = x
		self.y = 1 / (1 + np.exp(-x))
		return self.y

	def backward(self, grad):
		return grad * self.y * (1 - self.y)

class Tanh(Layer):
	# Tanh激活层
	def forward(self, x):
		self.x = x
		self.y = np.tanh(x)
		return self.y

	def backward(self, grad):
		return grad * (1 - self.y ** 2)

class ReLU(Layer):
	# Relu激活层
	def forward(self, x):
		self.x = x
		self.y = np.maximum(x, 0)
		return self.y

	def backward(self, grad):
		return grad * (self.x >= 0)

class Softmax(Layer):
	def forward(self, x):
		exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
		self.y = exp_x / np.sum(exp_x, axis=1, keepdims=True)
		return self.y
	
	def backward(self, grad):
		return grad

# 存储所有激活函数和对应名称,方便索引
activation_dict = {
	'identity': Identity,
	'sigmoid': Sigmoid,
	'tanh': Tanh,
	'relu': ReLU,
	'softmax': Softmax
}	 

2.2.4 MLP搭建

现在我们已经有了用来构建MLP的所有层,现在把它们拼起来即可得到MLP。

class Linear(Layer):
    def __init__(self, num_in, num_out, use_bias=True):
        self.num_in = num_in  # 输入维度
        self.num_out = num_out  # 输出维度
        self.use_bias = use_bias  # 是否添加偏置

        # 参数的初始化(绝对不能初始化为0!不然后续计算失去意义)
        # 用正态分布来初始化W
        self.W = np.random.normal(loc=0, scale=1.0, size=(num_in, num_out))
        if use_bias:
            self.b = np.zeros((1, num_out))

    def forward(self, x):
        # 前向传播 y = xW + b
        # x的维度为(batch_size, num_in)
        self.x = x
        self.y = x @ self.W  # y的维度为(batch_size, num_out)
        if self.use_bias:
            self.y += self.b
        return self.y

    def backward(self, grad):
        # 反向传播,按照链式法则计算
        # grad的维度为(batch_size, num_out)
        # 梯度应该对batch_size去平均值
        # grad_W的维度应该与W相同,为(num_in, num_out)
        self.grad_W = self.x.T @ grad / grad.shape[0]
        if self.use_bias:
            # grad_b的维度与b相同,(1, num_out)
            self.grad_b = np.mean(grad, axis=0, keepdims=True)
        # 往上一层传递的grad维度应该为(batch_size, num_in)
        grad = grad @ self.W.T
        return grad

    def update(self, learning_rate):
        # 更新参数以完成梯度下降
        self.W -= learning_rate * self.grad_W
        if self.use_bias:
            self.b -= learning_rate * self.grad_b

2.3 MLP对数据集进行分类

MLP构建好之后,我们就可以开始进行训练了。由于输入维度为3,输出应该有三个类别

,因此输入和输出神经元都应该有3个。

随机梯度下降算法(SGD)

在机器学习中,随机梯度下降(Stochastic Gradient Descent, SGD)是一种非常常见的优化算法。与标准的梯度下降算法不同,SGD在每次迭代中使用一个或几个随机选取的样本来计算梯度,而不是使用全部训练数据。这样做的好处包括:

  1. 提高计算效率:对于大型数据集,使用全数据集计算梯度可能会非常耗时,而SGD只需要使用少量样本。
  2. 更好的泛化性能:由于每次迭代使用的样本不同,SGD的更新过程具有更多的随机性,这可以帮助模型跳出局部最优,获得更好的泛化性能。

下面是具体的训练过程:

import numpy as np
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

# 设置训练参数
num_epochs = 1000
learning_rate = 0.05
batch_size = 20
eps = 1e-7 # 用于防止除0,log(0)等问题

# 创建一个层大小依次为3, 8, 3的多层感知机
# 对于多分类问题,使用softmax作为输出层的激活函数
mlp = MLP(layer_sizes=[3, 8, 3], use_bias=True, activation='relu', out_activation='softmax')

# 记录损失和准确率 
train_losses = [] 
test_losses = [] 
train_accuracies = [] 
test_accuracies = []

for epoch in range(num_epochs):
    st = 0
	loss = 0.0
	while st < len(X_train):
		ed = min(st + batch_size, len(X_train))
		# 取出batch
		x_batch = X_train[st:ed]
		y_batch = y_train_onehot[st:ed]
		# 计算MLP的预测
		y_pred = mlp.forward(x_batch)
		# 计算损失
		batch_loss = -np.sum(np.log(y_pred + eps) * y_batch) / y_batch.shape[0]
		loss += batch_loss
		# 计算梯度并进行反向传播
		grad = y_pred - y_batch
		mlp.backward(grad)
		# 更新参数
		mlp.update(learning_rate)
		st = ed
	loss /= (len(X_train) / batch_size)
	train_losses.append(loss)
	# 计算训练准确率
	train_acc = np.mean(np.argmax(mlp.forward(X_train), axis=1) == y_train)
	train_accuracies.append(train_acc)
	# 计算测试损失和准确率
	test_loss = -np.sum(np.log(mlp.forward(X_test) + eps) * y_test_onehot) / y_test_onehot.shape[0]
	test_losses.append(test_loss)
	test_acc = np.mean(np.argmax(mlp.forward(X_test), axis=1) == y_test)
	test_accuracies.append(test_acc)
	if epoch % 100 == 0:
		print(f'Epoch {epoch}, Train Loss: {loss:.4f}, Test Loss: {test_loss:.4f}, Train Acc: {train_acc:.4f}, Test Acc: {test_acc:.4f}')

# 可视化训练和测试的损失与准确率
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(train_losses, label='Train Loss')
plt.plot(test_losses, label='Test Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Loss over Epochs')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(train_accuracies, label='Train Accuracy')
plt.plot(test_accuracies, label='Test Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.title('Accuracy over Epochs')
plt.legend()

plt.show()

2.4 训练结果

Pasted image 20240520170420.png

Loss over Epochs

  • 训练损失(Train Loss)和测试损失(Test Loss):从图中可以看到,训练损失和测试损失在训练初期迅速下降,在大约100个epoch后趋于平稳,接近于零。这表明模型在训练集和测试集上的拟合效果都很好。

Accuracy over Epochs

  • 训练准确率(Train Accuracy)和测试准确率(Test Accuracy):从图中可以看到,训练准确率和测试准确率在训练初期快速上升,大约在100个epoch后趋于稳定,并接近100%。这表明模型在训练集和测试集上都达到了很高的分类准确率。

简述

  • 收敛速度:训练损失和测试损失在初期迅速下降,模型收敛速度较快。
  • 过拟合与泛化:训练损失和测试损失几乎同时下降,并在训练后期保持稳定,表明模型没有出现明显的过拟合现象。
  • 最终表现:训练准确率和测试准确率均接近100%,表明模型在分类任务中表现优异,能够很好地泛化到未见过的数据。

总的来说,模型在这次训练中表现非常好,能够快速收敛并保持较高的准确率和较低的损失

附录:调用sklearn

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report, accuracy_score
import matplotlib.pyplot as plt

# 设置随机种子,确保数据生成的可重复性
np.random.seed(42)

# 随机生成三类数据,每类20个样本,数据维度为2
class1 = np.random.randn(20, 2) + [1, 1]  # 中心点在(1,1)
class2 = np.random.randn(20, 2) + [5, 5]  # 中心点在(5,5)
class3 = np.random.randn(20, 2) + [9, 1]  # 中心点在(9,1)

# 汇总数据和标签
X = np.vstack((class1, class2, class3))
y = np.array([0]*20 + [1]*20 + [2]*20)  # 分别标记为0, 1, 2

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 数据标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 构建BP神经网络
clf = MLPClassifier(hidden_layer_sizes=(10,), max_iter=1000, alpha=1e-4,
                    solver='sgd', verbose=10, random_state=1,
                    learning_rate_init=.1)

# 训练模型
clf.fit(X_train_scaled, y_train)

# 测试集上的预测
y_pred = clf.predict(X_test_scaled)

# 性能评估
print("Classification report for classifier %s:\n%s\n"
      % (clf, classification_report(y_test, y_pred)))
print("Accuracy: ", accuracy_score(y_test, y_pred))

# 绘制结果
def plot_decision_boundaries(X, y, model, ax):
    h = .02
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    ax.contourf(xx, yy, Z, alpha=0.8)
    ax.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k', marker='o', s=50, linewidth=1)
    ax.set_xlim(xx.min(), xx.max())
    ax.set_ylim(yy.min(), yy.max())
    ax.set_xticks(())
    ax.set_yticks(())

fig, ax = plt.subplots()
plot_decision_boundaries(X_train_scaled, y_train, clf, ax)
plt.title("Decision surface of neural network")
plt.show()

数据生成

  • 方式:数据是通过正态分布随机生成的,每一类数据都是二维的,并且每一类围绕不同的中心点分布。具体地,我使用了numpyrandom.randn函数生成标准正态分布(均值为0,标准差为1)的样本,然后将它们平移到指定的中心位置。
  • 中心点
    • 类别1的中心点为 [1, 1]
    • 类别2的中心点为 [5, 5]
    • 类别3的中心点为 [9, 1]
  • 样本数量:每类数据包含20个样本。

这种方式确保每类数据都具有一定的集中趋势,同时又有一些随机性,模拟实际情况中类别数据的分布。

BP神经网络构建

  • 隐藏层配置:网络配置了一个包含10个节点的隐藏层。这个隐藏层大小旨在为模型提供足够的学习能力,同时避免过度复杂化。
  • 最大迭代次数max_iter=1000,这是训练过程中最大的迭代次数,用以控制训练的持续时间,防止过拟合。
  • 学习率learning_rate_init=0.1,这个参数设定了权重更新的初始步长,较大的学习率可以加快学习进度,但也可能导致训练不稳定。
  • 正则化项alpha=1e-4,这是L2正则化系数,用于控制模型的复杂度,避免过拟合。
  • 优化器:使用随机梯度下降(SGD)作为训练算法,它是一种有效的优化方法,尤其适合大规模数据训练。
  • 激活函数:默认使用ReLU激活函数,这是目前神经网络中常用的激活函数,有助于解决梯度消失问题,加快收敛速度。
  • 训练输出verbose=10,这个设置使得在训练过程中每10个epoch就输出一次训练状态,便于监控训练进度。

分类结果

codelancera@CodelancerA MINGW64 /e/codelancera/OneDrive/Desktop/DM&ML
$ C:/ProgramData/anaconda3/python.exe "e:/codelancera/OneDrive/Desktop/DM&ML/BP_demo.py"
Iteration 1, loss = 1.49852335
Iteration 2, loss = 1.18474940
Iteration 3, loss = 0.87402893
Iteration 4, loss = 0.63535660
...
Iteration 48, loss = 0.00285642
Iteration 49, loss = 0.00280477
Iteration 50, loss = 0.00275642
Iteration 51, loss = 0.00271143
Training loss did not improve more than tol=0.000100 for 10 consecutive epochs. Stopping.
Classification report for classifier MLPClassifier(hidden_layer_sizes=(10,), learning_rate_init=0.1, max_iter=1000,
              random_state=1, solver='sgd', verbose=10):
              precision    recall  f1-score   support

           0       1.00      1.00      1.00         4
           1       1.00      1.00      1.00         2
           2       1.00      1.00      1.00         6

    accuracy                           1.00        12
   macro avg       1.00      1.00      1.00        12
weighted avg       1.00      1.00      1.00        12


Accuracy:  1.0

Pasted image 20240519164213.png